package io.itit.ecp.admin.provide.action.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import icu.mhb.mybatisplus.plugln.core.JoinLambdaWrapper;
import icu.mhb.mybatisplus.plugln.extend.Joins;
import io.itit.ecp.admin.common.constants.ParamConstants;
import io.itit.ecp.admin.common.enums.IEnergyDataEnums;
import io.itit.ecp.admin.common.enums.IMeterDeviceEnums;
import io.itit.ecp.admin.provide.action.req.EnergyDataQueryReq;
import io.itit.ecp.admin.provide.action.req.EnergyDataReq;
import io.itit.ecp.admin.provide.action.resp.EnergyDataResp;
import io.itit.ecp.admin.provide.filter.IEnergyDataFilter;
import io.itit.ecp.admin.provide.helper.CarbonEmissionFactorHelper;
import io.itit.ecp.admin.provide.helper.SceneHelper;
import io.itit.ecp.admin.provide.renderer.EnergyDataRenderer;
import io.itit.ecp.admin.server.entity.*;
import io.itit.ecp.admin.server.entity.extend.EnergyDataExtEntity;
import io.itit.ecp.admin.server.entity.extend.PhysicalModelExt;
import io.itit.ecp.admin.server.service.*;
import io.itit.grass.common.exception.GrassException;
import io.itit.grass.common.provide.action.req.BaseBatchIdReq;
import io.itit.grass.common.provide.action.req.BaseIdReq;
import io.itit.grass.common.provide.action.resp.ResultResp;
import io.itit.grass.common.provide.action.validation.IAdd;
import io.itit.grass.common.provide.action.validation.IExport;
import io.itit.grass.common.provide.action.validation.IQueryPage;
import io.itit.grass.common.provide.action.validation.IRequired;
import io.itit.grass.common.server.utils.ConvertUtils;
import io.itit.grass.estelle.starter.core.ExcelXSSFUtil;
import io.itit.grass.estelle.starter.parse.ExcelParseManage;
import io.itit.grass.estelle.starter.util.JsonUtil;
import io.itit.grass.web.wall.utils.PageUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *
 * @Author：Roy
 * @Description：能源数据控制类
 * @Date：Create by 2023-12-05 11:48:22
 */
@Api(tags = "能源数据")
@RestController
@RequestMapping("/ecp-admin/energyData")
public class EnergyDataController {

    @Resource
    private IEnergyDataService energyDataService;

    @Resource
    private IMeterDeviceService meterDeviceService;

    @Resource
    private IDeviceWarningRecordService deviceWarningRecordService;

    @Resource
    private IEarlyWarningRecordService earlyWarningRecordService;

    @Resource
    private IConvertStandardCoalCoefficientService convertStandardCoalCoefficientService;

    @Resource
    private IDeviceService deviceService;

    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @ApiOperation("分页查询")
    @PostMapping("queryPage")
    @RequiresPermissions("ecp-admin:energyData:search")
    public ResultResp<IPage<EnergyDataResp>> queryPage(@RequestBody @Validated(IQueryPage.class) EnergyDataQueryReq req) {
        Page<EnergyDataResp> page = buildQW(req).joinPage(PageUtils.getPage(req, EnergyDataResp.class), EnergyDataResp.class);
        return ResultResp.success(SceneHelper.i().setSceneInfo(page));
    }


    @ApiOperation("根据设备Id分页查询")
    @PostMapping("queryPageByMeterDevice")
    public ResultResp<IPage<EnergyDataResp>> queryPageByMeterDevice(@RequestBody @Validated(IQueryPage.class) EnergyDataQueryReq req) {
        LocalDateTime start = StringUtils.isNotEmpty(req.getSolidifyDateStart()) ? YearMonth.parse(req.getSolidifyDateStart(), DateTimeFormatter.ofPattern("yyyy-MM"))
                .atDay(1).atTime(LocalTime.MIN) : null;
        LocalDateTime end = StringUtils.isNotEmpty(req.getSolidifyDateEnd()) ? YearMonth.parse(req.getSolidifyDateStart(), DateTimeFormatter.ofPattern("yyyy-MM"))
                .atEndOfMonth().atTime(LocalTime.MAX) : null;
        Page<EnergyDataResp> page = Joins.of(EnergyDataEntity.class)
                .eq(EnergyDataEntity::getMeterDeviceId, req.getMeterDeviceId())
                .ge(Objects.nonNull(start), EnergyDataEntity::getSolidifyDate, start)
                .le(Objects.nonNull(end), EnergyDataEntity::getSolidifyDate, end)
                .orderByDesc(StringUtils.isEmpty(req.getSortColumn()), EnergyDataEntity::getSolidifyDate)
                .joinPage(PageUtils.getPage(req, EnergyDataResp.class), EnergyDataResp.class);
        return ResultResp.success(SceneHelper.i().setSceneInfo(page));
    }

    @ApiOperation("详情")
    @PostMapping("detail")
    @RequiresPermissions("ecp-admin:energyData:search")
    public ResultResp<EnergyDataResp> detail(@RequestBody @Validated(IRequired.class) BaseIdReq req) {
        return ResultResp.success(ConvertUtils.copy(energyDataService.getById(req.getId()), EnergyDataResp.class));
    }

    /**
     * 组装查询条件
     */
    private static JoinLambdaWrapper<EnergyDataEntity> buildQW(EnergyDataQueryReq req) {
        Date solidifyDateStart = null;
        Date solidifyDateEnd = null;
        if (StringUtils.isNotEmpty(req.getSolidifyDateStart())) {
            solidifyDateStart = DateUtil.beginOfDay(DateUtil.parse(req.getSolidifyDateStart(), "yyyy-MM-dd"));
        }
        if (StringUtils.isNotEmpty(req.getSolidifyDateEnd())) {
            solidifyDateEnd = DateUtil.endOfDay(DateUtil.parse(req.getSolidifyDateEnd(), "yyyy-MM-dd"));
        }
        return Joins.of(EnergyDataEntity.class)
                .leftJoin(SceneProjectEntity.class, SceneProjectEntity::getId, EnergyDataEntity::getSceneProjectId)
                .select(SceneProjectEntity::getProjectName)
                .end()
                .leftJoin(EnergyConsumptionTypeEntity.class, EnergyConsumptionTypeEntity::getId, EnergyDataEntity::getEnergyTypeId)
                .selectAs(t -> t.addFunAlias(EnergyConsumptionTypeEntity::getEnergyName, EnergyDataExtEntity::getEnergyTypeName)
                        .addFunAlias(EnergyConsumptionTypeEntity::getEnergyUnit, EnergyDataExtEntity::getEnergyTypeUnit))
                .end()
                .leftJoin(SceneCompanyEntity.class, SceneCompanyEntity::getId, EnergyDataEntity::getSceneCompanyId)
                .select(SceneCompanyEntity::getCompanyName)
                .end()
                .leftJoin(MeterDeviceEntity.class, MeterDeviceEntity::getId, EnergyDataEntity::getMeterDeviceId)
                .selectAs(t -> t.addFunAlias(MeterDeviceEntity::getDeviceCode, EnergyDataExtEntity::getDeviceCode)
                                .addFunAlias(MeterDeviceEntity::getDeviceType, EnergyDataExtEntity::getDeviceType)
                                .addFunAlias(MeterDeviceEntity::getReadMeterType, EnergyDataExtEntity::getReadMeterType)
                                .addFunAlias(MeterDeviceEntity::getReadMeterTag, EnergyDataExtEntity::getReadMeterTag)
//                        .addFunAlias(MeterDeviceEntity::getDeviceName, EnergyDataExtEntity::getDeviceName)
                                .addFunAlias(MeterDeviceEntity::getStaticsType, EnergyDataResp::getStaticsType)
//                        .addFunAlias(MeterDeviceEntity::getProductTypeId, EnergyDataResp::getProductTypeId)
                )
                .end()
                .leftJoin(ProductTypeEntity.class, ProductTypeEntity::getId, EnergyDataEntity::getProductTypeId)
                .selectAs(ProductTypeEntity::getProductTypeName, EnergyDataResp::getProductTypeName)
                .end()
                .leftJoin(CarbonEmissionRangeEntity.class, CarbonEmissionRangeEntity::getId, EnergyDataEntity::getCalculationRange)
                .selectAs(CarbonEmissionRangeEntity::getRangeName, EnergyDataResp::getCalculationRangeName)
                .end()
                .eq(req.getSolidifyDate() != null, EnergyDataEntity::getSolidifyDate, req.getSolidifyDate())
                .ge(Objects.nonNull(solidifyDateStart), EnergyDataEntity::getSolidifyDate, solidifyDateStart)
                .le(Objects.nonNull(solidifyDateEnd), EnergyDataEntity::getSolidifyDate, solidifyDateEnd)
                .eq(StringUtils.isNotEmpty(req.getEnergyTypeId()), EnergyDataEntity::getEnergyTypeId, req.getEnergyTypeId())
                .eq(StringUtils.isNotEmpty(req.getSceneProjectId()), EnergyDataEntity::getSceneProjectId, req.getSceneProjectId())
                .eq(StringUtils.isNotEmpty(req.getSceneEdificeId()), EnergyDataEntity::getSceneEdificeId, req.getSceneEdificeId())
                .eq(StringUtils.isNotEmpty(req.getSceneFloorId()), EnergyDataEntity::getSceneFloorId, req.getSceneFloorId())
                .eq(StringUtils.isNotEmpty(req.getSceneRoomId()), EnergyDataEntity::getSceneRoomId, req.getSceneRoomId())
                .eq(StringUtils.isNotEmpty(req.getSceneCompanyId()), EnergyDataEntity::getSceneCompanyId, req.getSceneCompanyId())
                .eq(StringUtils.isNotEmpty(req.getMeterDeviceId()), EnergyDataEntity::getMeterDeviceId, req.getMeterDeviceId())
                .eq(StringUtils.isNotEmpty(req.getReadMeterType()), EnergyDataEntity::getReadMeterType, req.getReadMeterType())
                .eq(StringUtils.isNotEmpty(req.getReadMeterTag()), EnergyDataEntity::getReadMeterTag, req.getReadMeterTag())
                .eq(req.getReadTime() != null, EnergyDataEntity::getReadTime, req.getReadTime())
                .eq(req.getReadValue() != null, EnergyDataEntity::getReadValue, req.getReadValue())
                .eq(req.getMeterValue() != null, EnergyDataEntity::getMeterValue, req.getMeterValue())
                .eq(req.getCarbonEmission() != null, EnergyDataEntity::getCarbonEmission, req.getCarbonEmission())
                .eq(StringUtils.isNotEmpty(req.getCarbonEmissionProcess()), EnergyDataEntity::getCarbonEmissionProcess, req.getCarbonEmissionProcess())
                .eq(StringUtils.isNotEmpty(req.getDeptId()), EnergyDataEntity::getDeptId, req.getDeptId())
                .in(CollectionUtils.isNotEmpty(req.getIds()), EnergyDataEntity::getId, req.getIds())
                .last(StringUtils.isNotEmpty(req.getSortColumn()), PageUtils.lastOrderBy(req))
                .orderByDesc(StringUtils.isEmpty(req.getSortColumn()), EnergyDataEntity::getSolidifyDate)
                .orderByDesc(EnergyDataEntity::getCreateTm);
    }

    @ApiOperation("新增")
    @PostMapping("add")
    @RequiresPermissions("ecp-admin:energyData:add")
    public ResultResp<Boolean> add(@RequestBody @Validated({IAdd.class}) EnergyDataReq req) {
        Date date = req.getSolidifyDateStart();
        Date solidifyEnd = req.getSolidifyDateEnd();
        if (Objects.isNull(date) || Objects.isNull(solidifyEnd)) {
            return ResultResp.fail("数据时间不能为空");
        }
        BigDecimal meterValue = req.getMeterValue();
        List<EnergyDataEntity> energyDataEntities = new ArrayList<>();
        BigDecimal lastReadValue = getLastReadValue(req.getDeviceCode());
        do {
            req.setSolidifyDate(date);
            calcCarbonEmission(req);
            checkDataExists(req);
            EnergyDataEntity energyDataEntity = ConvertUtils.convertIgnoreId(req, EnergyDataEntity.class);
            energyDataEntity.setSolidifyDate(date);
            energyDataEntity.setReadValue(lastReadValue.add(energyDataEntity.getMeterValue()));
            energyDataEntity.setStandardCoal(getStandardCoalValue(energyDataEntity.getMeterValue(), energyDataEntity.getEnergyTypeId(), energyDataEntity.getSolidifyDate()));
            energyDataEntities.add(energyDataEntity);
            lastReadValue = energyDataEntity.getReadValue();
            date = DateUtils.addMinutes(date, 15); // 每15分钟生成一条数据
        } while (date.before(solidifyEnd) || date.equals(solidifyEnd));
        boolean res = false;
        if (CollectionUtils.isNotEmpty(energyDataEntities)) {
            BigDecimal avgMeterValue = meterValue.divide(new BigDecimal(String.valueOf(energyDataEntities.size())), 2, RoundingMode.HALF_UP);
            for (EnergyDataEntity energyDataEntity : energyDataEntities) {
                energyDataEntity.setMeterValue(avgMeterValue);
            }
            res = energyDataService.saveBatch(energyDataEntities, 500);
            if (res) {
                threadPoolTaskExecutor.execute(() -> {
                    // 触发设备预警
                    triggerDeviceWarnIfNecessary(energyDataEntities);
                    // 碳排放预警,设备计量预警
                    earlyWarningRecordService.triggerWarning(energyDataEntities.get(0).getMeterDeviceId(), req.getSolidifyDateStart(), req.getSolidifyDateEnd());
                });
            }
        }
        return ResultResp.success(res);
    }

    private BigDecimal getLastReadValue(String deviceCode) {
        return energyDataService.lambdaQuery()
                .eq(EnergyDataEntity::getDeviceCode, deviceCode)
                .orderByDesc(EnergyDataEntity::getSolidifyDate)
                .last(ParamConstants.LIMIT_ONE)
                .oneOpt().map(EnergyDataEntity::getReadValue).orElse(BigDecimal.ZERO);
    }

    // 标准煤:  计用量*拆标准煤系数=标准煤
    private BigDecimal getStandardCoalValue(BigDecimal meterValue, String energyTypeId, Date solidifyDate) {
        ConvertStandardCoalCoefficientEntity coalCoefficientEntity = convertStandardCoalCoefficientService.findFirstByEnergyTypeIdAndSolidifyDate(energyTypeId, solidifyDate);
        BigDecimal val = Objects.isNull(coalCoefficientEntity) ? BigDecimal.ZERO : ObjectUtil.defaultIfNull(coalCoefficientEntity.getCoefficient(), BigDecimal.ZERO);
        return meterValue.multiply(val);
    }

    private void triggerDeviceWarnIfNecessary(List<EnergyDataEntity> energyDataEntities) {
        List<EnergyDataEntity> energyDataEntityList = energyDataEntities.stream()
                .filter(energyDataEntity -> StringUtils.isNotEmpty(energyDataEntity.getMeterDeviceId()))
                .collect(Collectors.toList());
        List<PhysicalModelExt> physicalModelExts = new ArrayList<>();
        String deviceId = null;
        for (EnergyDataEntity energyDataEntity : energyDataEntityList) {
            String meterDeviceId = energyDataEntity.getMeterDeviceId();
            MeterDeviceEntity meterDeviceEntity = meterDeviceService.getById(meterDeviceId);
            if (Objects.nonNull(meterDeviceEntity)) {
                deviceId = meterDeviceEntity.getDeviceId();
                physicalModelExts = StringUtils.isNotEmpty(deviceId) ? deviceService.queryModelByDeviceId(deviceId) : Collections.emptyList();
                if (CollectionUtils.isNotEmpty(physicalModelExts)) {
                    break; // 这里批量添加的数据，都是属于同一个计量设备
                }
            }
        }
        //设备预警
        deviceWarningRecordService.triggerWarning(deviceId, physicalModelExts);
    }

    private void calcCarbonEmission(EnergyDataReq req) {
        Pair<BigDecimal, String> pair = CarbonEmissionFactorHelper.i().calcDay(req.getEnergyTypeId(), req.getSolidifyDate(), req.getMeterValue());
        req.setCarbonEmission(pair.getKey());
        req.setCarbonEmissionProcess(pair.getValue());
        req.setReadType(IEnergyDataEnums.ReadType.readType2.readType);
        setMeterDeviceId(req);
    }

    private void setMeterDeviceId(EnergyDataReq req) {
        if (StringUtils.isEmpty(req.getMeterDeviceId()) && StringUtils.isNotEmpty(req.getDeviceCode())) {
            List<MeterDeviceEntity> meterDeviceEntities = meterDeviceService.lambdaQuery()
                    .eq(MeterDeviceEntity::getSceneProjectId, req.getSceneProjectId())
                    .eq(MeterDeviceEntity::getDeviceCode, req.getDeviceCode())
                    .eq(MeterDeviceEntity::getDelFlag, IMeterDeviceEnums.DelFlag.delFlag1.delFlag)
                    .orderByDesc(MeterDeviceEntity::getCreateTm)
                    .list();
            if (CollectionUtils.isNotEmpty(meterDeviceEntities)) {
                req.setMeterDeviceId(meterDeviceEntities.get(0).getId());
            }
        }
    }

    private void checkDataExists(EnergyDataReq req) {
        Long count = energyDataService.lambdaQuery()
                .eq(EnergyDataEntity::getSolidifyDate, req.getSolidifyDate())
                .eq(EnergyDataEntity::getMeterDeviceId, req.getMeterDeviceId())
                .ne(StringUtils.isNotEmpty(req.getId()), EnergyDataEntity::getId, req.getId())
                .count();
        GrassException.throwEx(count > 0, "数据已存在！");
    }

    @ApiOperation("编辑")
    @PostMapping("update")
    @RequiresPermissions("ecp-admin:energyData:update")
    public ResultResp<Boolean> update(@RequestBody EnergyDataReq req) {
        EnergyDataEntity energyDataEntity = energyDataService.getById(req.getId());
        if (Objects.isNull(energyDataEntity)) {
            return ResultResp.fail("更新数据不存在");
        }
        if (Objects.isNull(req.getMeterValue())) {
            return ResultResp.fail("更新计量数值不能为空");
        }
        //仅允许编辑计量值
        //读表值：修改了计量值 ，读表值=未修改前的读表值-未修改前的计量值+修改后的计量值
        energyDataEntity.setReadValue(energyDataEntity.getReadValue().subtract(energyDataEntity.getMeterValue()).add(req.getMeterValue()));
        //计量值：修改计量值后将重新计算对应的碳排放量、碳减排放量等关联数据。
        energyDataEntity.setMeterValue(req.getMeterValue());
        setMeterDeviceId(req);
        if (StringUtils.isNotEmpty(req.getMeterDeviceId())) {
            energyDataEntity.setMeterDeviceId(req.getMeterDeviceId());
        }
        return ResultResp.success(energyDataService.updateById(energyDataEntity));
    }

    @ApiOperation("删除(根据Id删)")
    @PostMapping("deleteById")
    @RequiresPermissions("ecp-admin:energyData:deleteById")
    public ResultResp<Boolean> deleteById(@RequestBody @Validated({IRequired.class}) BaseIdReq req) {
        return ResultResp.success(energyDataService.removeById(req.getId()));
    }

    @ApiOperation("批量删除")
    @PostMapping("batchDelete")
    @RequiresPermissions("ecp-admin:energyData:batchDelete")
    public ResultResp<Boolean> batchDelete(@RequestBody @Validated({IRequired.class}) BaseBatchIdReq req) {
        return ResultResp.success(energyDataService.removeBatchByIds(req.getIds(), 1000));
    }

    @ApiOperation("导入")
    @PostMapping("upload")
    @RequiresPermissions("ecp-admin:energyData:upload")
    public ResultResp<Integer> upload(@RequestParam("file") MultipartFile file, Boolean ignoreError) {
        return ResultResp.success(ExcelParseManage.i().process(EnergyDataRenderer.ENTITY_NAME, file, ignoreError));
    }

    @ApiOperation("导出")
    @PostMapping("export")
    @RequiresPermissions("ecp-admin:energyData:export")
    public void export(@RequestBody @Validated(IExport.class) EnergyDataQueryReq req, HttpServletResponse response) {
        List<EnergyDataResp> respList = SceneHelper.i().setSceneInfo(buildQW(req).joinList(EnergyDataResp.class));
        ExcelXSSFUtil.export("EnergyDataExport", JsonUtil.toJson(respList, new IEnergyDataFilter.EnergyDataExtExportFilter()), response);
    }

}
